#pragma once

#include <iostream>
#include <vector>
#include <random>
#include <sstream>
#include <fstream>
#include <atomic>
#include <iomanip>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sqlite3.h>
#include "mq_log.hpp"

namespace ns_helper
{
    using namespace Ns_Log;
    class SqliteHelper
    {
    public:
        typedef int (*SqliteCallback)(void *, int, char **, char **);
        // int(*)(void*,int,char**,char**)返回值为 int 类型, 接受四个参数：一个 void* 指针，一个 int，以及两个 char** 类型的指针
        // 将 int(*)(void*,int,char**,char**); 类型的函数指针重命名为SqliteCallback
        SqliteHelper(const std::string &dbfile) : _handler(nullptr), _dbfile(dbfile)
        {
        }
        bool open(int safe_leve = SQLITE_OPEN_FULLMUTEX)
        {
            int ret = sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | safe_leve, nullptr);
            if (ret != SQLITE_OK)
            {
                LOG(FATAL) << "数据库sqlite3创建/打开失败:" << sqlite3_errmsg(_handler) << std::endl;
                return false;
            }
            return true;
        }
        void close()
        {
            if (_handler)
                sqlite3_close_v2(_handler); // 推荐使⽤--⽆论如何都会返回SQLITE_OK
        }

        bool exec(const std::string &sql, SqliteCallback cb, void *arg)
        {
            // arg 指定数据存储位置
            // SqliteCallback函数指针类型的回调函数
            int res = sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr);
            if (res != SQLITE_OK)
            {
                std::cout << sql << std::endl;
                LOG(ERROR) << "语句执行失败:" << sqlite3_errmsg(_handler) << std::endl;
                return false;
            }
            return true;
        }

    private:
        std::string _dbfile;
        sqlite3 *_handler;
    };
    class SplitHelper
    {
    public:
        static size_t split(const std::string &str, const std::string &sep, std::vector<std::string> &result)
        {
            size_t pos, idx = 0;
            while (idx < str.size())
            {
                // 从idx位置开始查找sep
                pos = str.find(sep, idx);
                if (pos == std::string::npos)
                {
                    result.push_back(str.substr(idx));
                    return result.size();
                }
                if (idx == pos)
                {
                    idx = pos + sep.size();
                    continue;
                }
                result.push_back(str.substr(idx, pos - idx));
                idx = pos + sep.size();
            }
            return result.size();
        }
    };
    class UUIDHelper
    {
    public:
        static std::string uuid()
        {
            std::random_device rd;
            size_t num = rd(); // 生成机器随机数, 效率较低
            // 通过机器随机数作为生成伪随机数种子,
            std::mt19937_64 gernator(rd());
            // 限制数字范围
            std::uniform_int_distribution<int> distribution(0, 255);
            // 将生成的数转化为16进制的数字
            std::stringstream ss; // ss << std::hex 设置流状态, 将数字格式化为16进制
            // ss << std::setw(2) << std::setfill('0'); // 位宽设置为2, 不足2位填充0
            // ss << std::hex << distribution(gernator);
            for (int i = 0; i < 8; i++)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << distribution(gernator); // 注意每次使用都要设置
                if (i == 3 || i == 5 || i == 7)
                    ss << "-"; // 设置格式 8位 - 4位 - 4位 - 序号
            }
            static std::atomic<int> cnt(1); // 这里一定要使用static, 保证程序运行时每次调用不进行初始化
            size_t n = cnt.fetch_add(1);
            for (int i = 7; i >= 0; i--)
            {
                ss << std::setw(2) << std::setfill('0') << std::hex << ((n >> (i * 8)) & 0xff);
                if (i == 6)
                    ss << "-";
            }
            return ss.str();
        }
    };
    class FileHelper
    {
    public:
        FileHelper(const std::string &filename) : _filename(filename)
        {
        }
        bool IsExist()
        {
            struct stat st;
            // 获取文件属性 为0文件存在，返回-1文件不存在
            if (stat(_filename.c_str(), &st) == 0)
            {
                return true;
            }
            return false;
        }
        size_t Size()
        {
            struct stat st;
            // 获取文件属性 为0文件存在，返回-1文件不存在
            if (stat(_filename.c_str(), &st) < 0)
            {
                return 0;
            }
            return st.st_size;
        }

        bool Read(char* s, size_t offset, size_t len) // 从哪个位置读, 读多少
        {
            // 1.打开文件
            std::ifstream in(_filename, std::ios::binary | std::ios::in);
            if (!in.is_open())
            {
                LOG(FATAL) << "文件打开失败\n";
                return false;
            }
            // 2.跳转到文件指定位置
            in.seekg(offset, std::ios::beg); // 从文件开始位置跳offset位
            // 3.读数据
            in.read(s, len);
            if (in.good() == false)
            {
                in.close();
                LOG(FATAL) << "文件读取失败\n";
                return false;
            }
            // 4.关闭文件
            in.close();
            return true;
        }
        bool Read(std::string &res) // 直接读
        {
            ssize_t file_size = this->Size();
            res.resize(file_size);
            return Read(&res[0], 0, file_size);
        }
        
        bool Write(const char *body, size_t offset, size_t len) // 指定位置写
        {
            // 1.打开文件;
            // 注意没有读权限无法跳转的文件指定位置, 所以这里既要有读权限, 又要有写权限
            std::fstream fs(_filename, std::ios::binary | std::ios::in | std::ios::out);
            if (!fs.is_open())
            {
                LOG(FATAL) << "文件打开失败\n";
                return false;
            }
            // 2.跳转到指定位置
            fs.seekg(offset, std::ios::beg);
            // 3.写入数据
            fs.write(body, len);
            if (fs.good() == false)
            {
                fs.close();
                LOG(FATAL) << "文件写入失败\n";
                return false;
            }
            // 4.关闭文件
            fs.close();
            return true;
        }
        bool Write(const std::string &str) // 直接写入
        {
            // 这里存在问题: fstream进行文件写入时不会清空原始文件内容
            // 比如:原文件100字节, 默认覆盖写50字节, 那文件的大小还是100字节, 后50字节依然存在
            // return Write(str, 0, str.size());
            std::ofstream out(_filename);
            if (!out.is_open())
            {
                return false;
            }
            out.write(str.c_str(), str.size());
            out.close();
            return true;
        }
        static bool createFile(const std::string &filename)
        {
            std::fstream fs(filename, std::ios::binary | std::ios::out);
            if (fs.is_open() == false)
            {
                LOG(FATAL) << filename << " 文件创建失败\n";
                return false;
            }
            fs.close();
            return true;
        }
        static bool removeFile(const std::string &filename)
        {
            return (remove(filename.c_str()) == 0);
        }
        static bool createDirectory(const std::string &path)
        {
            // aaa/bbb/ccc/ddd
            size_t pos, idx = 0;
            while (idx < path.size())
            {
                pos = path.find('/', idx);
                if (pos == std::string::npos)
                {
                    return mkdir(path.c_str(), 0775) == 0;
                }
                std::string subpath = path.substr(0, pos);
                int ret = mkdir(subpath.c_str(), 0775);
                if (ret != 0 && errno != EEXIST)
                {
                    LOG(ERROR) << "目录创建失败:" << subpath << std::endl;
                    return false;
                }
                idx = pos + 1;
            }
            return true;
        }
        static bool removeDirectory(const std::string &path)
        {
            std::string cmd = "rm -rf " + path;
            return system(cmd.c_str()) != -1;
        }
        static std::string parentDirectory(const std::string &filename)
        {
            size_t pos = filename.find_last_of("/");
            if (pos == std::string::npos)
            {
                return "./";
            }
            std::string path = filename.substr(0, pos);
            return path;
        }

        bool Rename(const std::string &nname)
        {
            return (::rename(_filename.c_str(), nname.c_str()) == 0);
        }

    private:
        std::string _filename;
    };
}